package com.elric.mall.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.elric.mall.common.enums.ExcelDataType;
import com.elric.mall.common.utils.ImportExcelUtil;
import com.elric.mall.dto.*;
import com.elric.mall.enums.ImportType;
import com.elric.mall.mapper.InvestCalcMapper;
import com.elric.mall.mapper.PlanProcessNodeMapper;
import com.elric.mall.mapper.PlanProjectMapper;
import com.elric.mall.mapper.ProgrammeProjectMapper;
import com.elric.mall.model.*;
import com.elric.mall.service.DictionaryService;
import com.elric.mall.service.PlanProjectService;
import com.github.pagehelper.PageHelper;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 实现功能：
 *
 * @author 作者[Yuxuan.he]
 * @version 1.0.00  2021-09-16 11:21
 */
@Service
public class PlanProjectServiceImpl implements PlanProjectService {
    @Autowired
    private PlanProjectMapper planProjectMapper;

    @Autowired
    private PlanProcessNodeMapper planProcessNodeMapper;

    @Autowired
    private ProgrammeProjectMapper programmeProjectMapper;

    @Autowired
    private InvestCalcMapper investCalcMapper;

    @Autowired
    private DictionaryService dictionaryService;

    private static final String INVEST_CALC_TYPE = "PLAN";

    private void initProjectInfo(PlanProject planProject, UmsAdmin umsAdmin) {
        planProject.setId(IdUtil.fastUUID());
        planProject.setCreateDate(new DateTime());
        planProject.setCreatorId(umsAdmin.getId());
        planProject.setCreatorName(umsAdmin.getUsername());
    }

    private void initNodeInfo(PlanProcessNode node, UmsAdmin umsAdmin) {
        node.setId(IdUtil.fastUUID());
        node.setCreateDate(new DateTime());
        node.setCreatorId(umsAdmin.getId());
        node.setCreatorName(umsAdmin.getUsername());
    }

    private void initInvestCalcInfo(InvestCalc investCalc, UmsAdmin umsAdmin) {
        investCalc.setId(IdUtil.fastUUID());
        investCalc.setCreateDate(new DateTime());
        investCalc.setCreatorId(umsAdmin.getId());
        investCalc.setCreatorName(umsAdmin.getUsername());
        investCalc.setType(INVEST_CALC_TYPE);
    }

    @Override
    public int create(PlanProject planProject, UmsAdmin umsAdmin) {
        initProjectInfo(planProject, umsAdmin);
        return planProjectMapper.insert(planProject);
    }

    @Override
    public int update(PlanProject planProject) {
        return planProjectMapper.updateByPrimaryKeySelective(planProject);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public PlanProjectDto save(PlanProjectDto dto, UmsAdmin umsAdmin) {
        PlanProject planProject = new PlanProject();
        BeanUtils.copyProperties(dto, planProject);
        if (StrUtil.isNotBlank(planProject.getId())){
            planProjectMapper.updateByPrimaryKeySelective(planProject);

            // 先删除所有关联项目进度信息
            PlanProcessNodeExample example = new PlanProcessNodeExample();
            example.createCriteria().andProjectIdEqualTo(planProject.getId());
            planProcessNodeMapper.deleteByExample(example);

            //删除所有投资匡算信息
            InvestCalcExample investCalcExample = new InvestCalcExample();
            investCalcExample.createCriteria().andProjectCodeEqualTo(planProject.getId());
            investCalcMapper.deleteByExample(investCalcExample);
        } else {
            initProjectInfo(planProject, umsAdmin);
            planProjectMapper.insert(planProject);
        }

        // 插入项目进度信息
        List<PlanProcessNode> nodes = dto.getProcessList();
        nodes.stream().forEach(n -> {
            initNodeInfo(n, umsAdmin);
            n.setProjectId(planProject.getId());
        });
        planProcessNodeMapper.insertBatch(nodes);

        // 插入投资匡算信息
        List<InvestCalc> calcs = dto.getInvestCalcList();
        calcs.stream().forEach(c -> {
            initInvestCalcInfo(c, umsAdmin);
            c.setProjectCode(planProject.getId());
        });
        if(CollectionUtil.isNotEmpty(calcs)){
            investCalcMapper.insertBatch(calcs);
        }
        return dto;
    }

    @Override
    public PlanProjectDto findById(String id) {
        PlanProjectDto dto = new PlanProjectDto();
        PlanProject planProject = planProjectMapper.selectByPrimaryKey(id);
        BeanUtils.copyProperties(planProject, dto);
        // 查询项目进度信息
        PlanProcessNodeExample example = new PlanProcessNodeExample();
        example.createCriteria().andProjectIdEqualTo(id);
        List<PlanProcessNode> list = planProcessNodeMapper.selectByExample(example);
        dto.setProcessList(list);
        // 查询投资匡算信息
        InvestCalcExample investCalcExample = new InvestCalcExample();
        investCalcExample.createCriteria().andProjectCodeEqualTo(planProject.getId());
        List<InvestCalc> investCalcs = investCalcMapper.selectByExample(investCalcExample);
        dto.setInvestCalcList(investCalcs);
        return dto;
    }

    @Override
    public List<ProgrammeManageDto> getProgrammeById(String id) {
        return programmeProjectMapper.getProgrammeByRelId(id);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int delete(String id) {
        // 删除项目进度信息
        PlanProcessNodeExample example = new PlanProcessNodeExample();
        example.createCriteria().andProjectIdEqualTo(id);
        planProcessNodeMapper.deleteByExample(example);

        // 删除投资匡算信息
        InvestCalcExample investCalcExample = new InvestCalcExample();
        investCalcExample.createCriteria().andProjectCodeEqualTo(id);
        investCalcMapper.deleteByExample(investCalcExample);

        return planProjectMapper.deleteByPrimaryKey(id);
    }

    private PlanProjectExample getExampleByModel(PlanProject planProject) {
        PlanProjectExample example = new PlanProjectExample();
        PlanProjectExample.Criteria criteria = example.createCriteria();
        if(!StringUtils.isEmpty(planProject.getGcmc())) {
            criteria.andGcmcLike("%"+planProject.getGcmc()+ "%");
        }
        if(!StringUtils.isEmpty(planProject.getXmmc())) {
            criteria.andXmmcLike("%" + planProject.getXmmc() + "%");
        }
        if(!StringUtils.isEmpty(planProject.getJqmc())) {
            criteria.andJqmcLike("%" + planProject.getJqmc() + "%");
        }
        if(!StringUtils.isEmpty(planProject.getXmdm())) {
            criteria.andXmdmLike("%" + planProject.getXmdm() + "%");
        }
        if(!StringUtils.isEmpty(planProject.getBz())) {
            criteria.andBzLike("%" + planProject.getBz() + "%");
        }
        if (!Objects.isNull(planProject.getHasChild())) {
            criteria.andHasChildEqualTo(planProject.getHasChild());
        }
        if (CollectionUtil.isNotEmpty(planProject.getGcList())) {
            criteria.andGcmcIn(planProject.getGcList());
        }
        if (CollectionUtil.isNotEmpty(planProject.getJqList())) {
            criteria.andJqmcIn(planProject.getJqList());
        }
        example.setOrderByClause("xmdm asc");
        return example;
    }

    @Override
    public List<PlanProject> list(Integer pageSize, Integer pageNum, PlanProject planProject) {
        PageHelper.startPage(pageNum, pageSize);
        PlanProjectExample example = getExampleByModel(planProject);
        return planProjectMapper.selectByExampleWithBLOBs(example);
    }

    /**
     * 校验导入数据
     * @param excelEntities
     */
    private void validateImportData(List<PlanProjectImportDto> excelEntities) {
        excelEntities.stream().forEach(e -> {
            List<String> errorList = new ArrayList<>();
            if (StrUtil.isNotBlank(e.getValidateMessage())){
                errorList.add(e.getValidateMessage());
            }
            if (StrUtil.length(e.getXmdm()) != 16) {
                errorList.add("【项目编码】字段长度必须为16位");
            }
            if (!StrUtil.endWith(e.getXmdm(), "X")){
                errorList.add("【项目编码】字段必须以【X】结尾");
            }
            if (CollectionUtil.isNotEmpty(errorList)) {
                e.setValidateMessage(CollectionUtil.join(errorList, ";"));
            }

        });
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public List<PlanProjectImportDto> importDocuments(MultipartHttpServletRequest request) {
        List<PlanProjectImportDto> excelEntities = ImportExcelUtil.readExcelByRequest(request, new PlanProjectImportDto());
        // 校验excel数据
        validateImportData(excelEntities);
        return excelEntities;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int insertBatch(List<PlanProject> list, ImportType importType) {
        if (importType == ImportType.full) {
            PlanProjectExample example = new PlanProjectExample();
            planProjectMapper.deleteByExample(example);
        }
        list.stream().forEach(p -> {
            p.setId(IdUtil.fastUUID());
        });
        return planProjectMapper.insertBatch(list);
    }

    /**
     * 根据excel导入dto将项目节点数据转化成行保存
     * @param dto
     * @param nodes
     * @param umsAdmin
     */
    private void setPlanProcessNodeByDto(PlanProjectImportDto dto, List<PlanProcessNode> nodes, UmsAdmin umsAdmin) {
        if (!Objects.isNull(dto)) {
            if (StrUtil.isNotBlank(dto.getProcess2021())) {
                PlanProcessNode p = new PlanProcessNode(dto.getId(), "2021", dto.getProcess2021());
                initNodeInfo(p, umsAdmin);
                nodes.add(p);
            }
            if (StrUtil.isNotBlank(dto.getProcess2022())) {
                PlanProcessNode p = new PlanProcessNode(dto.getId(), "2022", dto.getProcess2022());
                initNodeInfo(p, umsAdmin);
                nodes.add(p);
            }
            if (StrUtil.isNotBlank(dto.getProcess2023())) {
                PlanProcessNode p = new PlanProcessNode(dto.getId(), "2023", dto.getProcess2023());
                initNodeInfo(p, umsAdmin);
                nodes.add(p);
            }
            if (StrUtil.isNotBlank(dto.getProcess2024())) {
                PlanProcessNode p = new PlanProcessNode(dto.getId(), "2024", dto.getProcess2024());
                initNodeInfo(p, umsAdmin);
                nodes.add(p);
            }
            if (StrUtil.isNotBlank(dto.getProcess2025())) {
                PlanProcessNode p = new PlanProcessNode(dto.getId(), "2025", dto.getProcess2025());
                initNodeInfo(p, umsAdmin);
                nodes.add(p);
            }
            if (StrUtil.isNotBlank(dto.getProcess2026())) {
                PlanProcessNode p = new PlanProcessNode(dto.getId(), "2026", dto.getProcess2026());
                initNodeInfo(p, umsAdmin);
                nodes.add(p);
            }
            if (StrUtil.isNotBlank(dto.getProcess2027())) {
                PlanProcessNode p = new PlanProcessNode(dto.getId(), "2027", dto.getProcess2027());
                initNodeInfo(p, umsAdmin);
                nodes.add(p);
            }
        }
    }

    /**
     * 根据excel导入dto将投资匡算明细转为行数据保存
     * @param dto
     * @param calcs
     * @param umsAdmin
     */
    private void setInvestCalcByDto(PlanProjectImportDto dto, List<InvestCalc> calcs, UmsAdmin umsAdmin) {
        if (!Objects.isNull(dto)) {
            if (!Objects.isNull(dto.getJyxljf())) {
                InvestCalc i = new InvestCalc(dto.getId(), "教育训练经费", dto.getJyxljf());
                initInvestCalcInfo(i, umsAdmin);
                calcs.add(i);
            }
            if (!Objects.isNull(dto.getHqf())) {
                InvestCalc i = new InvestCalc(dto.getId(), "后勤费", dto.getHqf());
                initInvestCalcInfo(i, umsAdmin);
                calcs.add(i);
            }
            if (!Objects.isNull(dto.getZbf())) {
                InvestCalc i = new InvestCalc(dto.getId(), "装备费", dto.getZbf());
                initInvestCalcInfo(i, umsAdmin);
                calcs.add(i);
            }
            if (!Objects.isNull(dto.getWjbdjf())) {
                InvestCalc i = new InvestCalc(dto.getId(), "武警部队经费", dto.getWjbdjf());
                initInvestCalcInfo(i, umsAdmin);
                calcs.add(i);
            }
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int insertExcelBatch(List<PlanProjectImportDto> list, UmsAdmin umsAdmin, ImportType importType) {
        if (importType == ImportType.full) {
            PlanProjectExample example = new PlanProjectExample();
            planProjectMapper.deleteByExample(example);
        }
        // 构建项目实体和进度实体
        List<PlanProject> planProjects = new ArrayList<>();
        List<PlanProcessNode> planProcessNodes = new ArrayList<>();
        List<InvestCalc> investCalcs = new ArrayList<>();
        list.stream().forEach(p -> {
            PlanProject planProject = new PlanProject();
            BeanUtils.copyProperties(p, planProject);
            initProjectInfo(planProject, umsAdmin);
            p.setId(planProject.getId());
            planProjects.add(planProject);
            // 构建项目进度数据
            setPlanProcessNodeByDto(p, planProcessNodes, umsAdmin);
            setInvestCalcByDto(p, investCalcs, umsAdmin);
        });
        // 保存项目数据
        planProjectMapper.insertBatch(planProjects);
        // 保存项目进度数据
        planProcessNodeMapper.insertBatch(planProcessNodes);
        return investCalcMapper.insertBatch(investCalcs);
    }

    private String findDescription (List<PlanProcessNode> planProcessNodeList, String node, String id) {
        return planProcessNodeList.stream()
                .filter(i -> StrUtil.equals(id, i.getProjectId())&&StrUtil.equals(node, i.getPlanNode()))
                .findFirst().orElse(new PlanProcessNode()).getDescription();
    }

    /**
     * 根据工程、集群信息对规划项目进行排序
     * @param dtos
     * @return
     */
    private List<PlanProjectExportDto> formatByProjectAndGroup(List<PlanProjectExportDto> dtos) {
        List<PlanProjectExportDto> list = initExportHeader();
        if (CollectionUtil.isNotEmpty(dtos)){
            // 分别查询项目、集群信息list
            List<Dictionary> projects = dictionaryService.findByType("PROJECT_NAME");
            List<Dictionary> groups = dictionaryService.findByType("GROUP_NAME");
            projects.stream().forEach(p -> {
                // 找出当前工程下的计划信息
                List<PlanProjectExportDto> matchProjectDtos = dtos.stream().filter(d -> StrUtil.equals(d.getGcmc(), p.getName())).collect(Collectors.toList());
                // 只添加有对应工程信息的数据
                if (CollectionUtil.isNotEmpty(matchProjectDtos)) {
                    PlanProjectExportDto projectDto = new PlanProjectExportDto();
                    int proNum = Objects.isNull(p.getOrderNum())?0: p.getOrderNum();
                    projectDto.setNum(Convert.numberToChinese(proNum, false));
                    // 设置合并列状态
                    projectDto.setRegion(true);
                    projectDto.setStartNum(1);
                    projectDto.setEndNum(2);
                    projectDto.setXmdm(p.getName());
                    list.add(projectDto);
                    // 筛选出当前工程下的集群信息（当前工程下的计划包含该的集群即为该工程下的集群）
                    List<Dictionary> matchGroups = groups.stream()
                            .filter(g -> matchProjectDtos.stream().anyMatch(mp -> StrUtil.equals(g.getName(), mp.getJqmc())))
                            .collect(Collectors.toList());
                    matchGroups.stream().forEach(mg -> {
                        PlanProjectExportDto groupDto = new PlanProjectExportDto();
                        int groupNum = Objects.isNull(mg.getOrderNum())?0: mg.getOrderNum();
                        groupDto.setNum("(" + Convert.numberToChinese(groupNum, false) + ")");
                        // 设置合并列状态
                        groupDto.setRegion(true);
                        groupDto.setStartNum(1);
                        groupDto.setEndNum(2);
                        groupDto.setXmdm(mg.getName());
                        // 添加集群合并列
                        list.add(groupDto);
                        // 将匹配到集群的计划信息添加到list中
                        matchProjectDtos.stream()
                                .filter(mp -> StrUtil.equals(mp.getJqmc(), mg.getName()))
                                .forEach(mp -> list.add(mp));
                    });
                }
            });
        }
        return list;
    }

    /**
     * 初始化数据导出dto清单
     * @return
     */
    private List<PlanProjectExportDto> initExportHeader() {
        List<PlanProjectExportDto> list = new ArrayList<>();
        XSSFColor xssfColor = new XSSFColor(new java.awt.Color(255, 255, 255));

        // 抬头中间第一行"项目清单标题"
        PlanProjectExportDto title1 = new PlanProjectExportDto();
        title1.setNum("规划项目台账");
        title1.setAlignment(HorizontalAlignment.CENTER);
        title1.setStartNum(0);
        title1.setEndNum(12);
        title1.setRegion(true);
        title1.setDataType(ExcelDataType.header.name());
        title1.setFillForegroundColor(xssfColor);
        title1.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        title1.setFontSize((short) 24);
        title1.setCellHeight((short) 750);
        title1.setBorder(false);
        title1.setFontName("黑体");
        list.add(title1);

        // 抬头单位"万元"
        PlanProjectExportDto title2 = new PlanProjectExportDto();
        title2.setProcess2025("金额单位：亿元");
        title2.setStartNum(8);
        title2.setEndNum(12);
        title2.setRegion(true);
        title2.setDataType(ExcelDataType.header.name());
        title2.setFillForegroundColor(xssfColor);
        title2.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        title2.setFontSize((short) 10);
        title2.setCellHeight((short) 300);
        title2.setAlignment(HorizontalAlignment.RIGHT);
        title2.setBorder(false);
        title2.setFontName("楷体");
        list.add(title2);
        return list;
    }


    @Override
    public List<PlanProjectExportDto> findExportByModel(PlanProject planProject) {
        PlanProjectExample example = getExampleByModel(planProject);
        List<PlanProject> projectList = planProjectMapper.selectByExampleWithBLOBs(example);
        PlanProcessNodeExample processNodeExample = new PlanProcessNodeExample();
        processNodeExample.createCriteria()
                .andProjectIdIn(projectList.stream().map(PlanProject::getId).collect(Collectors.toList()));
        List<PlanProcessNode> planProcessNodeList = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(projectList)) {
            planProcessNodeList = planProcessNodeMapper.selectByExample(processNodeExample);
        }
        final Integer[] index = {1};
        List<PlanProcessNode> finalPlanProcessNodeList = planProcessNodeList;
        List<PlanProjectExportDto> dtoList = projectList.stream().map(p -> {
            PlanProjectExportDto dto = new PlanProjectExportDto();
            BeanUtils.copyProperties(p, dto);
            dto.setNum(Integer.toString(index[0]));
            // 获取投资相关金额
            dto.setProcess2021(findDescription(finalPlanProcessNodeList, "2021", p.getId()));
            dto.setProcess2022(findDescription(finalPlanProcessNodeList, "2022", p.getId()));
            dto.setProcess2023(findDescription(finalPlanProcessNodeList, "2023", p.getId()));
            dto.setProcess2024(findDescription(finalPlanProcessNodeList, "2024", p.getId()));
            dto.setProcess2025(findDescription(finalPlanProcessNodeList, "2025", p.getId()));
            dto.setProcess2026(findDescription(finalPlanProcessNodeList, "2026", p.getId()));
            dto.setProcess2027(findDescription(finalPlanProcessNodeList, "2027", p.getId()));
            index[0]++;
            return dto;
        }).collect(Collectors.toList());
        List<ProgrammeManageDto> programmeProjectList = programmeProjectMapper.getLinkProgrammeList();
        List<PlanProjectExportDto> result = new ArrayList<>();
        dtoList.stream().forEach(d -> {
            result.add(d);
            AtomicInteger programmeIndex = new AtomicInteger(1);
            programmeProjectList.stream()
                    .filter(pl -> StrUtil.equals(pl.getRelId(), d.getId())).forEach(programmeManageDto -> {
                        PlanProjectExportDto dto = new PlanProjectExportDto();
                        BeanUtils.copyProperties(programmeManageDto, dto);
                        dto.setJqmc(d.getJqmc());
                        dto.setGcmc(d.getGcmc());
                        dto.setJhxmdm(dto.getXmdm());
                        dto.setXmdm("");
                        dto.setNum(d.getNum() + "-" + programmeIndex);
                        programmeIndex.getAndIncrement();
                        result.add(dto);
                    });
        });
        return formatByProjectAndGroup(result);
    }
}
